package contegor.scire.core;

import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * {@link Connectable} implementation.
 * Stores internally its creation time.
 * Follows 'lazy' pattern building its internal structures.
 *  
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
@Version("$Id$")
public class ConnectableImpl implements Connectable, Serializable, Cloneable {
  private static final long serialVersionUID = -1540214052866494545L;

  /**
   * Transactions will use this enum as a modification mark.
   * Only with the commit the modifications will be realy done.
   * @see ContainerImpl.
   */
  public enum Status {
    UNTOUCHED,
    CREATED,
    MODIFIED,
    ERASED,
    FIXED // For unmodifiable elements.
  }

  /** Map of the <code>{@link LinkImpl}</code> that come from this <code>ConnectableImpl</code>.*/
  private ConcurrentNavigableMap<Id,LinkImpl> links;
  protected long creationTime;
  protected AtomicReference<TransactionId> transactionId;
  protected AtomicReference<Status> status;


  /** Package private constructor so this object can't be instantiated outside it. */
  protected ConnectableImpl(long creationTime, TransactionId transactionId) {
    this.links           = null;
    this.creationTime    = creationTime;
    this.transactionId   = new AtomicReference<TransactionId>(transactionId);
    this.status          = new AtomicReference<Status>((transactionId != null) ? Status.CREATED : Status.UNTOUCHED);
  }

  protected final ConcurrentNavigableMap<Id,LinkImpl> getLinksTable() {
    if (links == null) {
      synchronized(this) {
        if (links == null)
          links = new ConcurrentSkipListMap<Id,LinkImpl>();
      }
    }
    return links;
  }

  public Status getStatus() {
    return this.status.get();
  }

  public void setStatus(Status status) {
    this.status.set(status);
  }

  @Override
  public Map<Id,Link> getLinks() {
    return Collections.unmodifiableMap((Map<Id,? extends Link>)getLinksTable());
  }

  @Override
  public int getLinksCount() {
    if (links == null)
      return 0;
    return links.size();
  }

  @Override
  public Link getLink(Id target) {
    if (links == null)
      return null;
    return links.get(target);
  }

  @Override
  public Map<Id, Link> getLinks(Id from, Id to) {
    if (links == null)
      return null;
    return Collections.unmodifiableMap((Map<Id,? extends Link>)links.subMap(from, true, to, true));
  }

  @Override
  public Map<Id, Link> getTopLinks(Id from) {
    if (links == null)
      return null;
    return Collections.unmodifiableMap((Map<Id,? extends Link>)links.tailMap(from, true));
  }

  @Override
  public Map<Id, Link> getBottomLinks(Id from) {
    if (links == null)
      return null;
    return Collections.unmodifiableMap((Map<Id,? extends Link>)links.headMap(from, true));
  }

  @Override
  public Link getCeilLink(Id id) {
    if (links == null)
      return null;
    Map.Entry<Id, LinkImpl> entry = links.ceilingEntry(id);
    if (entry == null)
      return null;
    return entry.getValue();
  }

  @Override
  public Link getFloorLink(Id id) {
    if (links == null)
      return null;
    Map.Entry<Id, LinkImpl> entry = links.floorEntry(id);
    if (entry == null)
      return null;
    return entry.getValue();
  }

  @Override
  public boolean isLinkedWith(Id target) {
    if (links == null)
      return false;
    return links.containsKey(target);
  }

  @Override
  public long getCreationTime() {
    return this.creationTime;
  }

  @Override
  public boolean equals(Object rhs) {
    if (this == rhs)
      return true;
    if (rhs == null)
      return false;
    if (!(rhs instanceof ConnectableImpl))
      return false;
    ConnectableImpl obj = (ConnectableImpl)rhs;
    if (this.creationTime != obj.getCreationTime())
      return false;
    if (this.links == obj.links) // ... both can be null.
      return true;
    if ((this.links == null) || (obj.links == null))
      return false;
    // At this point and for practical reasons, we assume that
    // if both Connectables have the same amount of links, they are equal.
    // Note: The system does not allow more than one link between to Connectables. So execution
    // almost never will reach this point.
    return this.links.size() == obj.links.size();
  }

  @Override
  public int hashCode() {
    return (int)this.creationTime;
  }

  @Override
  public boolean isVisibleTo(TransactionId transactionId) {
    if (this.transactionId.get() != null) {
      switch (this.status.get()) {
        case CREATED:
          return this.transactionId.get().equals(transactionId);
        case ERASED:
          return !this.transactionId.get().equals(transactionId);
        default:
          return true;
      }
    }
    return true;
  }

  protected boolean isModifiableBy(TransactionId transactionId) {
    if (this.transactionId.get() != null) {
      switch (this.status.get()) {
        case ERASED:
          return false;
        default:
          return this.transactionId.get().equals(transactionId);
      }
    }
    else if (this.status.get() == Status.FIXED)
      return false;
    else
      return true;
  }

  public ConnectableImpl clone() throws CloneNotSupportedException {
    ConnectableImpl lhs = (ConnectableImpl)super.clone();
    lhs.transactionId = new AtomicReference<TransactionId>(this.transactionId.get());
    lhs.status = new AtomicReference<Status>(this.status.get());
    if (this.links != null) {
      lhs.links = new ConcurrentSkipListMap<Id, LinkImpl>();
      for (Map.Entry<Id,LinkImpl> entry: this.links.entrySet())
        lhs.links.put(entry.getKey(), entry.getValue().clone());
    }
    return lhs;
  }

  /** 
   * Joins this <code>Connectable</code> with a <code>{@link LinkImpl}</code>.
   * This method only sets up the <code>Connectable</code> side of the connection.
   * @return <code>true</code> if the connections was successful. False if this connection already existed.
   */
  protected boolean connect(LinkImpl link) {
    return getLinksTable().putIfAbsent(link.getTarget(),link) == null;
  }
    
  /**
   * Breaks the union between this <code>Connectable</code> and a <code>{@link LinkImpl Link}</code>.
   * This method only erase the <code>Connectable</code> side of the connection.
   * 
   * @return <code>true</code> if the <code>Connectable</code> was joined with the <code>Link</code>; <code>false</code> if not. 
   */
  protected boolean disconnect(LinkImpl link) {
    assert (link.getTarget() != null);
    if (links == null)
      return false;
    return (links.remove(link.getTarget()) != null); 
  }

  @Override
  public String toString() {
    return "ConnectableImpl [creationTime=" + creationTime + ", status=" + status + ", transactionId=" + transactionId + "]";
  }
}